package com.itjeffrey.autocode.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.itjeffrey.autocode.bo.ScheduleJobBO;
import com.itjeffrey.autocode.common.PageResult;
import com.itjeffrey.autocode.common.Result;
import com.itjeffrey.autocode.constant.SysConstant;
import com.itjeffrey.autocode.entity.ScheduleJobTbEntity;
import com.itjeffrey.autocode.enums.ExceptionEnum;
import com.itjeffrey.autocode.exception.ACException;
import com.itjeffrey.autocode.mapper.ScheduleJobTbMapper;
import com.itjeffrey.autocode.quartz.bean.JobBean;
import com.itjeffrey.autocode.quartz.schedule.ScheduleContext;
import com.itjeffrey.autocode.service.ScheduleJobTbService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itjeffrey.autocode.util.ACBaseUtil;
import com.itjeffrey.autocode.util.BeanOprUtils;
import com.itjeffrey.autocode.util.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.quartz.Scheduler;
import org.quartz.Trigger;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;

/**
 * 服务实现类
 * @author: admin
 * @date: 2021-04-17 01:03:13
 */
@Service
@Slf4j
public class ScheduleJobTbServiceImpl extends ServiceImpl<ScheduleJobTbMapper, ScheduleJobTbEntity> implements ScheduleJobTbService {

    @Resource
    private Scheduler scheduler;

    @Resource
    private ScheduleJobTbMapper scheduleJobTbMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result add(ScheduleJobBO scheduleJobBO) {
        //check cron expression
        if(!ACBaseUtil.isValidCron(scheduleJobBO.getCron())){
            throw new ACException(ExceptionEnum.CRON_EXPRESSION_INVALID);
        }
        ScheduleJobTbEntity scheduleJobTbEntity = BeanOprUtils.copyProperties(scheduleJobBO, new ScheduleJobTbEntity());
        scheduleJobTbEntity.setStatus(SysConstant.SCHEDULE_JOB_STS_OFF);
        scheduleJobTbEntity.setCreateTime(LocalDateTime.now());
        int insert = scheduleJobTbMapper.insert(scheduleJobTbEntity);
        if(insert != 1){
            throw new ACException(ExceptionEnum.JOB_ADD_ERROR);
        }
        ScheduleContext.createJob(scheduler, BeanOprUtils.copyProperties(scheduleJobTbEntity, new JobBean()));
        return Result.ok();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result mod(ScheduleJobBO scheduleJobBO) {
        //check cron expression
        if(!ACBaseUtil.isValidCron(scheduleJobBO.getCron())){
            throw new ACException(ExceptionEnum.CRON_EXPRESSION_INVALID);
        }
        ScheduleJobTbEntity scheduleJobTbEntity = BeanOprUtils.copyProperties(scheduleJobBO, new ScheduleJobTbEntity());
        LambdaUpdateWrapper<ScheduleJobTbEntity> updateWrapper = new LambdaUpdateWrapper<ScheduleJobTbEntity>()
                .eq(ScheduleJobTbEntity::getJobId, scheduleJobBO.getJobId());
        int update = scheduleJobTbMapper.update(scheduleJobTbEntity, updateWrapper);
        if(update != 1){
            throw new ACException(ExceptionEnum.JOB_MOD_ERROR);
        }
        //更新完数据库中的Job对象后重启定时任务
        ScheduleContext.updateJob(scheduler, BeanOprUtils.copyProperties(scheduleJobBO, new JobBean()));
        return Result.ok();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result pauseJob(String id) {
        //update status `OFF`
        this.updateStatus(id, SysConstant.SCHEDULE_JOB_STS_OFF);
        //pause job
        ScheduleContext.pauseJob(scheduler, String.valueOf(id));
        return Result.ok();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result resumeJob(String id) {
        //update status `ON`
        this.updateStatus(id, SysConstant.SCHEDULE_JOB_STS_ON);
        //resume job
        ScheduleContext.resumeJob(scheduler, String.valueOf(id));
        return Result.ok();
    }

    /**
     * 更新任务状态
     * @param jobId 任务ID
     * @param newSts 新任务状态
     */
    @Override
    public void updateStatus(String jobId, String newSts){
        ScheduleJobTbEntity scheduleJobTbEntity = new ScheduleJobTbEntity();
        scheduleJobTbEntity.setStatus(newSts);
        LambdaUpdateWrapper<ScheduleJobTbEntity> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(ScheduleJobTbEntity::getJobId, jobId);
        scheduleJobTbMapper.update(scheduleJobTbEntity, updateWrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result deleteById(Integer id) {
        Result result = this.queryById(id);
        Object data = result.getData();
        if(data instanceof ScheduleJobBO){
            ScheduleJobBO scheduleJobBO = (ScheduleJobBO) data;
            JobBean jobBean = BeanOprUtils.copyProperties(scheduleJobBO, new JobBean());
            //查询任务状态，若任务还在运行中则无法删除
            Trigger.TriggerState triggerState = ScheduleContext.queryJobState(jobBean);
            if(Trigger.TriggerState.NORMAL == triggerState){
                throw new ACException(ExceptionEnum.JOB_STATE_ERROR);
            }
            scheduleJobTbMapper.deleteById(id);
        }
        return Result.ok();
    }

    @Override
    public Result queryById(Integer id) {
        ScheduleJobTbEntity scheduleJobTbEntity = scheduleJobTbMapper.selectById(id);
        ScheduleJobBO scheduleJobBO = BeanOprUtils.copyProperties(scheduleJobTbEntity, ScheduleJobBO::new);
        return Result.ok(scheduleJobBO);
    }

    @Override
    public Result queryList(ScheduleJobBO scheduleJobBO) {
        log.info("查询任务接口列表入参 -> {}", scheduleJobBO.toString());
        PageResult<ScheduleJobBO> pageResult = new PageResult<ScheduleJobBO>().setCurPageAndSize(scheduleJobBO
                .calCurPage());
        LambdaQueryWrapper<ScheduleJobTbEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(scheduleJobBO.getJobName()), ScheduleJobTbEntity::getJobName,
                SysConstant.SPECIAL_CHAR_1 + scheduleJobBO.getJobName() + SysConstant.SPECIAL_CHAR_1);
        queryWrapper.last(SysConstant.DB_KEYWORD_LIMIT + scheduleJobBO.getCurPage() + SysConstant.SPECIAL_CHAR_3 +
                scheduleJobBO.getSize()); //等价于limit 1,5 （有SQL注入风险）
        queryWrapper.orderByDesc(ScheduleJobTbEntity::getCreateTime);
        List<ScheduleJobTbEntity> scheduleJobTbEntities = scheduleJobTbMapper.selectList(queryWrapper);
        pageResult.setTotals(getTotalScheduleJobs(scheduleJobBO));
        pageResult.setData(BeanOprUtils.copyListProperties(scheduleJobTbEntities, ScheduleJobBO::new));
        return Result.ok(pageResult);
    }

    @Override
    public Result getJobId() {
        String jobId;
        String dateStr = DateUtil.formatLocalDatetimeOnYYYYMMDD(LocalDateTime.now());
        //get the scheduleJob of max create_time
        List<ScheduleJobTbEntity> scheduleJobTbEntities = scheduleJobTbMapper.selectList(new QueryWrapper<>());
        Optional<ScheduleJobTbEntity> max = scheduleJobTbEntities.stream().max(Comparator.comparing(ScheduleJobTbEntity::getCreateTime));
        if(!max.isPresent()){
            //if not exists, create new JobId based on the date of now
            jobId = SysConstant.SCHEDULE_JOB_COUNTER_PREFIX + dateStr + SysConstant.SCHEDULE_JOB_COUNTER_INITVAL;
        }else {
            //if exists, parse the scheduleJob, by the newest jobId of the scheduleJob, generate a new JobId
            ScheduleJobTbEntity scheduleJobTbEntity = max.get();
            String jobId1 = scheduleJobTbEntity.getJobId();
            String job1datetimestr = jobId1.substring(3, 11);
            if(dateStr.equals(job1datetimestr)){
                //the same day
                jobId = SysConstant.SCHEDULE_JOB_COUNTER_PREFIX + dateStr + ACBaseUtil.counter(jobId1.substring(11));
            }else {
                //the different day
                jobId = SysConstant.SCHEDULE_JOB_COUNTER_PREFIX + dateStr + SysConstant.SCHEDULE_JOB_COUNTER_INITVAL;
            }
        }
        return Result.ok(jobId);
    }

    @Override
    public Result queryByBeanName(String beanName) {
        if(StringUtils.isBlank(beanName)){
            return Result.ok(null);
        }
        LambdaQueryWrapper<ScheduleJobTbEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ScheduleJobTbEntity::getBeanName, beanName);
        List<ScheduleJobTbEntity> scheduleJobTbEntities = scheduleJobTbMapper.selectList(wrapper);
        ScheduleJobBO scheduleJobBO = CollectionUtils.isNotEmpty(scheduleJobTbEntities) ?
                BeanOprUtils.copyProperties(scheduleJobTbEntities.iterator().next(), ScheduleJobBO::new) : null;
        return Result.ok(scheduleJobBO);
    }

    private Integer getTotalScheduleJobs(ScheduleJobBO scheduleJobBO) {
        LambdaQueryWrapper<ScheduleJobTbEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(scheduleJobBO.getJobName()), ScheduleJobTbEntity::getJobName,
                SysConstant.SPECIAL_CHAR_1 + scheduleJobBO.getJobName() + SysConstant.SPECIAL_CHAR_1);
        return scheduleJobTbMapper.selectCount(queryWrapper);
    }
}